package la.bbs558.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.NumberUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import la.bbs558.context.PointContext;
import la.bbs558.dao.UserDao;
import la.bbs558.entity.*;
import la.bbs558.entity.enums.*;
import la.bbs558.entity.vo.UserVO;
import la.bbs558.exception.MsgException;
import la.bbs558.entity.params.*;
import la.bbs558.satoken.StpAdminUtil;
import la.bbs558.service.*;
import la.bbs558.util.Objs;
import la.bbs558.util.Strings;
import la.bbs558.util.ValidUtils;
import la.bbs558.websocket.WebsocketUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService {

    @Resource
    private UserDao userDao;
    @Resource
    private InvitationCodeService invitationCodeService;
    @Resource
    private UserDataService userDataService;
    @Resource
    private UserPointsLogService userPointsLogService;
    @Resource
    private UserRoleService userRoleService;
    @Resource
    private UserConfigService userConfigService;
    @Resource
    private VerifyCodeService verifyCodeService;
    @Resource
    private UserPushService userPushService;

    @Value("${user.default-avatar}")
    private String defaultAvatar;

    @Value("${user.default-sign}")
    private String defaultSign;

    @Value("${user.default-tel}")
    private String defaultTel;
    @Resource
    private UserVisitStatisticsService userVisitStatisticsService;

    @Override
    public UserVO signIn(UserSignInParams params) {
        String verifyCode = params.getVerifyCode();
        if (!Objs.isValid(verifyCode)) {
            throw new MsgException("请输入验证码");
        }
        String username = params.getUsername();
        RedisKey signInVerifyCode = RedisKey.SIGN_IN_VERIFY_CODE;
        verifyCodeService.verifyCode(signInVerifyCode,username, verifyCode);
        User user = lambdaQuery()
                .eq(User::getUsername, params.getUsername())
                .eq(User::getPassword, params.getPassword())
                .one();
        Objs.valid(user, "用户名不存在或密码错误");
        Optional<UserState> state = UserState.getByCode(user.getState());
        if (!state.isPresent() || state.get() == UserState.FALSE) {
            throw new MsgException("用户已被禁用");
        }
        // 设置最后一次登录时间
        Long userId = user.getId();
        boolean update = new User().setLoginTime(new Date()).setId(userId).updateById();
        if (!update) {
            throw new MsgException("更新登录数据出现错误");
        }
        verifyCodeService.removeCode(signInVerifyCode,username);
        UserVO userVO = new UserVO()
                .setAvatar(user.getAvatar())
                .setNickname(user.getNickname())
                .setSign(user.getSign());
        //关闭websocket
        WebsocketUtils.closeSession(userId);
        //踢出其他人登录账号
        StpUtil.kickout(userId, LoginType.WEB.name());
        StpUtil.login(userId, LoginType.WEB.name());
        userVO.setToken(StpUtil.getTokenValue());
        return userVO;
    }

    @Override
    public void listUserPageForAdmin(UserSearchPage searchPage) {
        // 处理搜索条件
        String loginDevice = searchPage.getLoginDevice();
        loginDevice = Strings.isEmpty(loginDevice) ? "" : loginDevice;
        loginDevice = Arrays.stream(loginDevice.split(",")).distinct().sorted().collect(Collectors.joining(","));
        searchPage.setLoginDevice(loginDevice);
        userDao.selectListForAdmin(searchPage);
    }

    @Override
    public User signInForAdmin(UserSignInParams params) {
        User user = lambdaQuery()
                .eq(User::getUsername, params.getUsername())
                .eq(User::getPassword, params.getPassword())
                .one();
        Objs.valid(user, "用户名不存在或密码错误");
        Optional<UserState> state = UserState.getByCode(user.getState());
        if (!state.isPresent()) {
            throw new MsgException("用户已被禁用");
        }
        Long userId = user.getId();
        UserRole userRole = userRoleService.lambdaQuery()
                .eq(UserRole::getUserId, userId)
                .like(UserRole::getRoleIds, "admin").one();
        Objs.valid(userRole, "该用户不是管理员");
        //关闭websocket
        WebsocketUtils.closeSession(userId);
        //踢出其他人登录账号
        StpAdminUtil.kickout(userId, LoginType.WEB.name());
        StpAdminUtil.login(userId, LoginType.WEB.name());
        return new User()
                .setNickname(user.getNickname())
                .setSign(user.getSign())
                .setTel(user.getTel())
                .setEmail(user.getEmail())
                .setAvatar(user.getAvatar());
    }

    @Override
    public void searchUserByPage(UserSearchPage searchPage) {
    }

    @Override
    public boolean validateEmail(String email) {
        Objs.valid(email, "邮箱或手机号不能为空");
        Integer count = null;
        if (NumberUtil.isNumber(email)) {
            count = lambdaQuery().select(User::getId).eq(User::getUsername, email).count();
        }
        if (ValidUtils.isEmail(email)) {
            count = lambdaQuery().select(User::getId).eq(User::getEmail, email).count();
        }
        if (count == null) {
            throw new MsgException("邮箱或手机号格式错误");
        }
        return count == 0;
    }

    @Override
    public void forGetPassword(UserForgetPasswordParams params) {
        String email = params.getEmail();
        String verifyCode = params.getVerifyCode();
        String password = params.getPassword();
        if (Objs.isAnyInvalid(email, verifyCode, password)) {
            throw new MsgException("参数错误");
        }
        verifyCodeService.verifyCode(RedisKey.FORGET_VERIFY_CODE,email, verifyCode);
        Optional<User> user = lambdaQuery().eq(User::getUsername, email).oneOpt();
        user.orElseThrow(() -> new MsgException("用户不存在"));
        if (user.get().getPassword().equals(password)) {
            throw new MsgException("新密码不能与旧密码相同");
        }
        Long id = user.get().getId();
        lambdaUpdate().set(User::getPassword, password).eq(User::getId, id).update();
        verifyCodeService.removeCode(RedisKey.FORGET_VERIFY_CODE,email);
        //把当前用户踢下线
        StpUtil.kickout(id);
    }

    @Override
    public void updateState(User user) {
        new User().setState(user.getState() == 1 ? 1 : 0).setId(user.getId()).updateById();
        if (user.getState() != 1) {
            // 如果是封禁
            StpUtil.logout(user.getId());
        }
    }

    @Override
    public void batchRegister(List<UserSignUpParams> userSignUpParams) {
        for (UserSignUpParams params : userSignUpParams) {
            try {
                String email = params.getEmail();
                String nickname = params.getNickname();
                Objs.isTrue(Strings.isNotEmpty(nickname), "昵称不能为空");
                Objs.isTrue(nickname.length() <= 20, "昵称不能超过20个字符");
                String avatar = Objs.isInvalid(params.getAvatar()) ? defaultAvatar : params.getAvatar();
                String sign = params.getSign();
                User user = new User()
                        .setUsername(email)
                        .setNickname(nickname)
                        .setPassword(params.getPassword())
                        .setAvatar(avatar)
                        .setEmail(email)
                        .setSign(sign)
                        .setTel(params.getTel());
                boolean save = user.insert();
                Objs.isTrue(save, "注册失败");
                Long userId = user.getId();
                //初始化用户数据
                userDataService.initGetUserData(userId);
                userConfigService.initGetUserConfig(userId);
                userRoleService.initUserRole(userId, "user");
                //设置邮箱加积分
                userPointsLogService.getPoints(new PointContext(userId, userId, PointsOptType.SET_EMAIL, UserOpt.SET_EMAIL));
            } catch (Exception e) {
                log.error("插入数据异常", e);
            }
        }
    }

    @Override
    public Object validatePhone(String phone) {
        Objs.valid(phone, "手机号码不能为空");
        Integer count = lambdaQuery().select(User::getId).eq(User::getUsername, phone).count();
        return count == 0;
    }

    @Override
    public void setLoginDeviceName(HttpServletRequest request, boolean isMobile) {
        // 根据userAgent判断设备类型
        String userAgent = request.getHeader("User-Agent");
        String deviceName = "未知设备";
        if (isMobile) {
            if (userAgent.contains("ios")) {
                deviceName = "iOS";
            } else {
                deviceName = "Android";
            }
        } else {
            if (userAgent.contains("Android")) {
                deviceName = "Android";
            } else if (userAgent.contains("iPhone")) {
                deviceName = "iOS";
            } else if (userAgent.contains("iPad")) {
                deviceName = "iOS";
            } else if (userAgent.contains("Windows")) {
                deviceName = "Windows";
            } else if (userAgent.contains("Macintosh")) {
                deviceName = "Mac";
            } else if (userAgent.contains("Linux")) {
                deviceName = "Linux";
            }
        }
        long userId = StpUtil.getLoginIdAsLong();
        User user = getById(userId);
        String loginDevice = user.getLoginDevice();
        loginDevice = Strings.isEmpty(loginDevice) ? deviceName : loginDevice + "," + deviceName;
        loginDevice = Arrays.stream(loginDevice.split(",")).distinct().sorted().collect(Collectors.joining(","));
        new User()
                .setId(userId)
                .setLoginDevice(loginDevice)
                .updateById();
    }

    @Override
    public UserVO signUp(UserSignUpParams params) {
        String inviteCode = params.getInviteCode();
        Objs.isTrue(Strings.isNotBlank(inviteCode), "邀请码无效");
        UserData userData = userDataService.verifyInviteCode(inviteCode.trim());
        Objs.valid(userData, "邀请码无效");
        String nickname = params.getNickname();
        Objs.isTrue(Strings.isNotEmpty(nickname), "昵称不能为空");
        Objs.isTrue(nickname.length() <= 20, "昵称不能超过20个字符");
        String username = params.getUsername() == null ? params.getEmail() : params.getUsername();
        //验证邮箱验、手机证码
        verifyCodeService.verifyCode(RedisKey.SIGN_UP_VERIFY_CODE,username, params.getVerifyCode().trim());
        //可以通过邮箱或者手机号码登录
        Objs.isTrue(validateEmail(username), "邮箱或手机号已被注册");
        boolean isPhone = NumberUtil.isNumber(username);
        String email = isPhone ? username : params.getEmail();

        String tel = params.getTel();
        User user = new User()
                .setUsername(username)
                .setNickname(nickname)
                .setPassword(params.getPassword())
                .setAvatar(defaultAvatar)
                .setEmail(email)
                .setTel(Strings.isEmpty(tel) ? defaultTel : tel)
                .setSign(defaultSign);
        boolean save = user.insert();
        Objs.isTrue(save, "注册失败");
        Long userId = user.getId();
        //添加邀请码记录
        invitationCodeService.save(new InvitationCode()
                .setCreateUser(userData.getUserId())
                .setRegisterUser(userId)
                .setCode(inviteCode));
        //初始化用户数据
        userDataService.initGetUserData(userId);
        userConfigService.initGetUserConfig(userId);
        userRoleService.initUserRole(userId, "user");
        //设置邮箱加积分
        if(!isPhone){
            userPointsLogService.getPoints(new PointContext(userId, userId, PointsOptType.SET_EMAIL, UserOpt.SET_EMAIL));
        }
        //更新邀请人的userdata
        userDataService.lambdaUpdate().setSql("invitation_cnt = invitation_cnt + 1").eq(UserData::getId, userData.getId()).update();
        verifyCodeService.removeCode(RedisKey.SIGN_UP_VERIFY_CODE,username);
        StpUtil.login(userId, LoginType.WEB.name());
        StpUtil.login(userId, LoginType.APP.name());
        return new UserVO()
                .setAvatar(user.getAvatar())
                .setNickname(user.getNickname())
                .setSign(user.getSign())
                .setToken(StpUtil.getTokenValue());
    }

    private boolean validPhone(String username) {
        Objs.valid(username, "手机号码不能为空");
        Integer count = lambdaQuery().select(User::getId).eq(User::getUsername, username).count();
        return count == 0;
    }

    @Override
    public UserVO signInForMobile(UserMobileSignInParams params) {
        User user = lambdaQuery()
                .eq(User::getUsername, params.getUsername())
                .eq(User::getPassword, params.getPassword())
                .one();
        Objs.valid(user, "用户名不存在或密码错误");
        Optional<UserState> state = UserState.getByCode(user.getState());
        if (!state.isPresent() || state.get() == UserState.FALSE) {
            throw new MsgException("用户已被禁用");
        }
        // 设置最后一次登录时间
        Long userId = user.getId();
        boolean update = new User().setLoginTime(new Date()).setId(userId).updateById();
        if (!update) {
            throw new MsgException("更新登录数据出现错误");
        }
        UserVO userVO = new UserVO()
                .setAvatar(user.getAvatar())
                .setNickname(user.getNickname())
                .setSign(user.getSign());
        //关闭websocket
        WebsocketUtils.closeSession(userId);
        //踢出其他人登录账号
        StpUtil.kickout(userId, LoginType.APP.name());
        StpUtil.login(userId, LoginType.APP.name());
        userVO.setToken(StpUtil.getTokenValue());
        // 插入推送标识
        userPushService.addPushInfo(new UserPush().setUserId(userId).setCid(params.getCid()));
        return userVO;
    }

}
